package org.micode.libs.remoteapi;

import org.micode.common.remote.annotaion.RemoteClient;
import org.micode.common.remote.proxy.JdkProxyCreator;
import lombok.extern.slf4j.Slf4j;
import org.micode.common.utils.YamlUtils;
import org.reflections.Reflections;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import java.util.Set;

/**
 * @author wutianbin
 * @create 2019/2/19
 */
@Component
@Lazy
@Slf4j
public class RemoteClientRegister implements BeanFactoryPostProcessor {

    /**
     * 动态注册Bean所需对象
     */
    private DefaultListableBeanFactory defaultListableBeanFactory;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory)
            throws BeansException {
        String remotePack = YamlUtils.instance.getValue("remote.service.package");
        log.debug("Running RemoteClientRegister.postProcessBeanFactory() {}", remotePack);
        this.defaultListableBeanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;

//        01 获取指定路径下的所有类类型
        Reflections reflections = new Reflections(remotePack);

//        02 获取标注了@RemoteClient注解的类类型
        Set<Class<?>> clients = reflections.getTypesAnnotatedWith(RemoteClient.class, true);

//        03 为标注了@RemoteClient 注解的所有类类型创建代理类并将该代理类注册到IOC容器中去
        for (Class cls : clients) {
            try {
                createProxyClass(cls);
            } catch (NoSuchMethodException e) {
                log.error("处理异常", e);
            }
        }
    }

    /**
     * 创建代理类并将代理类注册到IOC容器中去
     *
     * @param cls 需要创建代理类的的类类型
     * @throws NoSuchMethodException
     */
    private void createProxyClass(Class cls) throws NoSuchMethodException {

        // 一、创建代理类对象
        ClientInvocationHandler handler = getClientInvocationHandler();

        // 二、获取代理类的Bean定义
        BeanDefinition proxyBeanDefinition = getProxyBeanDefinition(cls, handler);

        // 三、将代理类的Bean信息注册到Spring容器中
        registerBeanDefinition(cls, proxyBeanDefinition);
    }

    /**
     * 将代理类的Bean信息注册到Spring容器中: Bean名称为接口名
     *
     * @param cls
     * @param proxyBeanDefinition
     */
    private void registerBeanDefinition(Class cls, BeanDefinition proxyBeanDefinition) {
        this.defaultListableBeanFactory.registerBeanDefinition(cls.getSimpleName(), proxyBeanDefinition);
    }

    /**
     * 获取JDK代理类的Bean定义: 根据目标接口的类型和处理器实例获取目标类接口的JDK代理类的Bean定义
     *
     * @param cls
     * @param handler
     * @return
     * @throws NoSuchMethodException
     */
    private BeanDefinition getProxyBeanDefinition(Class cls, ClientInvocationHandler handler)
            throws NoSuchMethodException {
        // 获取JDK代理类的类型
        Class<?> jdkDynamicProxyClass = getJDKDynamicProxyClass(cls, handler);

        // 获取JDK代理类的Bean定义
        BeanDefinition jdkBeanDefinition = getJDKBeanDefinition(jdkDynamicProxyClass, handler);

        return jdkBeanDefinition;
    }

    /**
     * 获取JDK代理类的Bean定义: 根据JDK代理类的类型和处理类实例创建JDK代理类的Bean定义
     *
     * @param jdkDynamicProxyClass
     * @param handler
     */
    private BeanDefinition getJDKBeanDefinition(Class<?> jdkDynamicProxyClass, ClientInvocationHandler handler) {
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
                .genericBeanDefinition(jdkDynamicProxyClass)
                .addConstructorArgValue(handler)
                .getBeanDefinition();
        beanDefinition.setAutowireCandidate(true);
        return beanDefinition;
    }

    /**
     * 获取JDK代理类的类类型
     *
     * @param cls
     * @param handler
     */
    private Class<?> getJDKDynamicProxyClass(Class cls, ClientInvocationHandler handler)
            throws NoSuchMethodException {
        JdkProxyCreator jdkProxyCreator = new JdkProxyCreator(new Class[]{cls}, handler);
        return jdkProxyCreator.getProxyClassInfo();
    }

    /**
     * 获取创建代理时所需的处理类
     *
     * @return
     */
    private ClientInvocationHandler getClientInvocationHandler() {
        DefaultClient defaultClient = new DefaultClient();
        return new ClientInvocationHandler(defaultClient);
    }

}
